<?php
/**
 * @package mikrokosmos
 * file created on 5-mar-2006
 *
 * jaco_at_pixeldump_dot_org
 */

if(!class_exists("MKTagHeader")){ class MKTagHeader {}}


/**
 * DefineSound
 */
class MKSoundContainer extends MKTagHeader {

	var $tagData = "";
	var $ss;				// an MKSoundStreamHead class
							// useful to automatically set playback settings

	// soundID is an alias of itemID

	var $format;	//UB[4] Format of SoundData
					// 0 = uncompressed
					// 1 = ADPCM
					// SWF 4 or later only:
					// 2 = MP3
					// 3 = uncompressed little-endian
					// SWF 6 or later only:
					//6 = Nellymoser

	var $rate = RATE_44;		// UB[2] The sampling rate.
					// 5.5kHz is not allowed for MP3.
					// 0 = 5.5 kHz
					// 1 = 11 kHz
					// 2 = 22 kHz
					// 3 = 44 kHz

	var $size = SOUND_16BIT;	// UB[1] Size of each sample. Always 16
					// bit for compressed formats.
					// May be 8 or 16 bit for
					// uncompressed formats.
					// 0 = snd8Bit
					// 1 = snd16Bit

	var $type = SOUND_MONO;	// UB[1] Mono or stereo sound
					// For Nellymoser: always 0
					// 0 = sndMono
					// 1 = sndStereo

	var $sampleCount;	// UI32 Number of samples. Not
					// affected by mono/stereo
					// setting; for stereo sounds this
					// is the number of sample pairs.

	var $soundData;	//

	var $sndDir = SERVER_MKSND;
	var $soundFileName = "";

	var $linkage;	// linkage

	// the constructor
	function MKSoundContainer($tagData = "", $sndFN = "", $sndDir = SERVER_MKSND, $soundID = 1, $sndType = "", $sndSize = ""){

		$this->tagData = $tagData;
		if(strlen($sndDir)) $this->sndDir = $sndDir;
		$this->soundFileName = $sndFN;
		$this->ss = new MKSoundStreamHead("", SOUND_SH2);
		$this->itemID = $soundID;
		if(strlen($sndType)) $this->type = $sndType;
		if(strlen($sndSize)) $this->size = $sndSize;

		if(strlen($tagData)){
			parent::MKTagHeader(substr($tagData, 0, 10));
			$this->get_sound_data();
		}
		if(strlen($sndFN)) $this->load_from_file($sndFN, $sndDir, $sndType, $sndSize);
	}

	//
	function set_soundID($soundID = 1) { $this->itemID = $soundID; }
	function get_soundID() { return $this->itemID; }

	//
	function set_format($format) { $this->format = $format; }
	function get_format() { return $this->format; }

	//
	function set_rate($rate) { $this->rate = $rate; }
	function get_rate() { return $this->rate; }

	//
	function set_size($size) { $this->size = $size; }
	function get_size() { return $this->size; }

	//
	function set_type($type) { $this->type = $type; }
	function get_type() { return $this->type; }

	//
	function set_sampleCount($sampleCount) { $this->sampleCount = $sampleCount; }
	function get_sampleCount() { return $this->sampleCount; }

	//
	function set_soundData($soundData) { $this->soundData = $soundData; }
	function get_soundData() { return $this->soundData; }

	//
	function set_tagData($tagData) {$this->tagData = $tagData; }
	function get_tagData(){ return $this->tagData; }

	//
	function set_sndDir($sndDir) {$this->sndDir = $sndDir; }
	function get_sndDir(){ return $this->sndDir; }

	//
	function set_soundFileName($soundFileName) {$this->soundFileName = $soundFileName; }
	function get_soundFileName(){ return $this->soundFileName; }

	//
	function update_linkage($linkageName = ""){
		if(!isset($this->linkage)) $this->linkage = new MKExportAssets();
		$this->linkage->update_asset($this->itemID, $linkageName, 0);

		// prevent empty linkage name
		// in case it provides an automated generated name
		$linkageRef = $this->linkage->get_assetAt(0);
		if(!strlen($linkageRef["name"])) $linkageName = "soundID" .$this->itemID;
		$this->linkage->update_asset($this->itemID, $linkageName, 0);

		return $this->linkage->update_tagData();
	}

	//
	function set_linkage_name($linkageName = ""){
		return $this->update_linkage($linkageName);
	}


	//
	function update_soundStreamHead(){
		$this->ss->set_pbRate($this->rate);
		$this->ss->set_pbSize($this->size);
		$this->ss->set_pbType($this->type);
		$this->ss->set_ssCompression(SOUND_MP3);
	}

	//
	function get_ss_tagData(){
		return $this->ss->update_tagData();
	}

	//
	function set_sound_data($sad = array()){

		if(count($sad)){
			$this->get_sound_data($sad);
		}
		else {
			$sad = $this->fetch_assoc();
		}

		$this->update_soundStreamHead();
		$this->tagData = write_defineSound_data($sad);
		return $this->tagData;
	}

	//
	function get_sound_data($sad = array()){

		if(!count($sad)){
			$sad = read_defineSound_data($this->tagData);
		}

		$this->itemID		= $sad["itemID"];
		$this->format		= $sad["format"];
		$this->rate			= $sad["rate"];
		$this->size			= $sad["size"];
		$this->type			= $sad["type"];
		$this->sampleCount  = $sad["sampleCount"];
		$this->soundData	= $sad["soundData"];

		return $sad;
	}

	//
	function fetch_assoc(){

		$sad = array();

		$sad["itemID"] = $this->itemID;
		$sad["format"] = $this->format;
		$sad["rate"] = $this->rate;
		$sad["size"] = $this->size;
		$sad["type"] = $this->type;
		$sad["sampleCount"] = $this->sampleCount;
		$sad["soundData"] = $this->soundData;

		return $sad;
	}

	//
	function fetch_xml($includeHeader = true, $tabLevel = 0, $nodeName = "soundItem"){

		$xmlStr = $includeHeader ? XML_HEADER : "";
		$pfx = get_xml_indent_row(++$tabLevel);

		$rate = (get_sound_rate($this->rate) / 1000) ."KHz";

		$xmlStr .= $pfx ."<" .$nodeName ." ";
		$xmlStr .= "id=\"";
		$xmlStr .= $this->itemID ."\" ";
		$xmlStr .= "tagSize=\"";
		$xmlStr .= strlen($this->tagData) ."\" ";
		$xmlStr .= "format=\"";
		$xmlStr .= get_sound_format($this->format) ."\" ";
		$xmlStr .= "rate=\"";
		$xmlStr .= $rate ."\" ";
		$xmlStr .= "sampleSize=\"";
		$xmlStr .= get_sampleSize($this->size) ."\" ";
		$xmlStr .= "type=\"";
		$xmlStr .= get_sound_type($this->type) ."\" ";
		$xmlStr .= "sampleCount=\"";
		$xmlStr .= $this->sampleCount ."\" ";
		$xmlStr .= ">";

		$xmlStr .= "<bufHeader>";
		$xmlStr .= hex_dump(substr($this->tagData, 0, 16));
		$xmlStr .= "</bufHeader>";
		$pfx = get_xml_indent_row(--$tabLevel);

		$xmlStr .= $pfx ."</" .$nodeName .">";
		return $xmlStr;

	}

	// insert sound data, supports mp3 and uncompressed .raw sound data (additional info needed)
	function load_from_file($sndFN = "", $sndDir = "", $sndType = "", $sndSize = ""){

		if(strlen($sndFN)){
			$ext = strtolower(substr($sndFN, strlen($sndFN) -4, 4));
			$this->soundFileName = $sndFN;
		}
		if(strlen($sndDir)) $this->sndDir = $sndDir;

		$sndPath = $this->sndDir .$this->soundFileName;

		if(strlen($sndType)) $this->sndType = $sndType;
		if(strlen($sndSize)) $this->sndSize = $sndSize;

		if(!file_exists($sndPath)) return false; // avoid to load inexistent files

		$snd = fopen($sndPath, "rb");
		$sndData = fread($snd, filesize($sndPath));
		fclose($snd);

		if($ext == ".mp3") {
			$this->format = SOUND_MP3;
			$mp3Data = read_mp3_data(substr($sndData, 0, 4096), filesize($sndPath));
			$mp3Data["soundData"] = $sndData;
			$mp3Data["itemID"] = $this->itemID;
			return $this->set_sound_data($mp3Data);
		}
		else if($ext == ".raw") { // uncompressed sound data
			$this->format = SOUND_UNCOMPRESSED_LE;
			$sad = $this->fetch_assoc();
			$sad["soundData"] = $sndData;

			$sc = filesize($sndPath);
			if($this->size == SOUND_16BIT) $sc /= 2;
			if($this->type == SOUND_STEREO) $sc /= 2;

			$sad["sampleCount"] = floor($sc);
			return $this->set_sound_data($sad);
		}
	}

	//
	function dump_soundData($sndDir = ""){

		if(!strlen($this->soundFileName)){
			$this->soundFileName = "sndgrab_" .time();
			$ext = get_sound_format($this->format);

			$this->soundFileName .= "." .$ext;
		}

		// if sndDir is given, create a SPAN sndDir to dump data
		$sndPath = strlen($sndDir) ? $sndDir : $this->sndDir;
		$sndPath .= $this->soundFileName;

		$sndOut = fopen($sndPath, "wb");
		fwrite($sndOut, $this->soundData, strlen($this->soundData));
		fclose($sndOut);
	}

	// alias of dump_soundData
	function save_to_file($sndDir = ""){ $this->dump_soundData($sndDir); }

	//
	function update_tagData(){
		return $this->set_sound_data();
	}

	// returns linkage tagData
	function update_linkage_tagData(){
		$this->update_linkage();
		return $this->linkage->update_tagData();
	}
}


/**
 * StartSound
 */
class MKStartSound  extends MKTagHeader {

	// soundID alias of itemID
	var $tagData = "";
	var $soundInfo;

	function MKStartSound($tagData = "", $soundID = 1){

		$this->itemID = $soundID;
		$this->tagData = $tagData;

		if(strlen($tagData)){
			parent::MKTagHeader(substr($tagData, 0, 10));
			$this->itemID = $soundID;
			$this->set_startSound_data();
		}
	}

	//
	function set_soundID($soundID) { $this->itemID = $soundID; }
	function get_soundID() { return $this->itemID; }

	//
	function set_soundInfo($soundInfo) { $this->soundInfo = $soundInfo; }
	function get_soundInfo() { return $this->soundInfo; }

	//
	function set_startSound_data($ssd = array()){

		if(count($ssd)) $this->get_sound_data($ssd);
		else $ssd = $this->fetch_assoc();

		$this->tagData = write_startSound_data($ssd);
		return $this->tagData;
	}

	//
	function get_startSound_data($ssd = array()){

		if(!count($ssd)) $ssd = read_startSound_data($this->tagData);

		$this->itemID = $ssd["itemID"];
		$this->soundInfo = new MKSoundInfo("", $ssd["soundInfo"]);

		return $ssd;
	}

	//
	function fetch_assoc(){

		$ssd = array();
		$ssd["soundID"]   = $this->itemID;
		$ssd["soundInfo"] = $this->soundInfo->fetch_assoc();

		return $ssd;
	}

	//
	function fetch_xml(){

	}

	//
	function update_tagData(){ return $this->set_startSound_data(); }
}


/**
 * SOUNDINFO
 */
class MKSoundInfo {

	var $buf = "";

	var $syncStop = 0;					// UB[1] Stop the sound now
	var $syncNoMultiple = 0;			// UB[1] Don�t start the sound if already playing
	var $hasEnvelope = 0;				// UB[1] Has envelope information
	var $hasLoops = 0;					// UB[1] Has loop information
	var $hasOutPoint = 0;				// UB[1] Has out-point information
	var $hasInPoint = 0;				// UB[1] Has in-point information
	var $inPoint;					// If HasInPoint UI32 Number of samples
									// to skip at beginning of sound
	var $outPoint;					// If HasOutPoint UI32 Position in samples
									// of last sample to play
	var $loopCount;					// If HasLoops UI16 Sound loop count
	var $envPoints;					// If HasEnvelope UI8 Sound Envelope point count
	var $envelopeRecords = array();	// If HasEnvelope SOUNDENVELOPE[EnvPoints] Sound Envelope

	// the constructor
	function MKSoundInfo($buf = "", $sid = array()){

		$this->buf = $buf;
		if(strlen($buf)) $this->get_soundInfo_data();
		else if(count($sid)) $this->get_soundInfo_data($sid);
	}

	//
	function set_syncStop($syncStop) { $this->syncStop = $syncStop; }
	function get_syncStop() { return $this->syncStop; }

	//
	function set_syncNoMultiple($syncNoMultiple) { $this->syncNoMultiple = $syncNoMultiple; }
	function get_syncNoMultiple() { return $this->syncNoMultiple; }

	//
	function set_hasEnvelope($hasEnvelope) { $this->hasEnvelope = $hasEnvelope; }
	function get_hasEnvelope() { return $this->hasEnvelope; }

	//
	function set_hasLoops($hasLoops) { $this->hasLoops = $hasLoops; }
	function get_hasLoops() { return $this->hasLoops; }

	//
	function set_hasOutPoint($hasOutPoint) { $this->hasOutPoint = $hasOutPoint; }
	function get_hasOutPoint() { return $this->hasOutPoint; }

	//
	function set_hasInPoint($hasInPoint) { $this->hasInPoint = $hasInPoint; }
	function get_hasInPoint() { return $this->hasInPoint; }

	//
	function set_inPoint($inPoint) { $this->inPoint = $inPoint; }
	function get_inPoint() { return $this->inPoint; }

	//
	function set_outPoint($outPoint) { $this->outPoint = $outPoint; }
	function get_outPoint() { return $this->outPoint; }

	//
	function set_loopCount($loopCount) { $this->loopCount = $loopCount; }
	function get_loopCount() { return $this->loopCount; }

	//
	function set_envPoints($envPoints) { $this->envPoints = $envPoints; }
	function get_envPoints() { return $this->envPoints; }

	//
	function set_envelopeRecords($envelopeRecords) { $this->envelopeRecords = $envelopeRecords; }
	function get_envelopeRecords() { return $this->envelopeRecords; }

	//
	function add_envelopeRecord($sed = array()){
		if(!count($sed)) return false;
		$erIdx = count($this->envelopeRecords);
		$this->envelopeRecords[$erIdx] = new MKSoundEnvelope("", $sed);
	}

	//
	function set_soundInfo_data($sid = array()){
		if(count($sid)) $this->get_soundInfo_data($sid);
		else $sid = $this->fetch_assoc();

		$this->tagData = write_soundInfo_data($sid);
		return $this->tagData;
	}

	//
	function get_soundInfo_data($sid = array()){

		if(!count($sid)) $sid = read_soundInfo_data($this->tagData);

		$this->syncStop		  = $sid["syncStop"];
		$this->syncNoMultiple = $sid["syncNoMultiple"];
		$this->hasEnvelope	  = $sid["hasEnvelope"];
		$this->hasLoops		  = $sid["hasLoops"];
		$this->hasOutPoint	  = $sid["hasOutPoint"];
		$this->hasInPoint	  = $sid["hasInPoint"];

		if($sid["hasInPoint"]) 	$this->inPoint	 = $sid["inPoint"];
		if($sid["hasOutPoint"])	$this->outPoint	 = $sid["outPoint"];
		if($sid["hasLoops"]) 	$this->loopCount = $sid["loopCount"];

		if($sid["hasEnvelope"]){

			$this->envPoints = $sid["envPoints"];
			$this->envelopeRecords = array();

			foreach($sid["envelopeRecords"] as $erd){
				$this->add_envelopeRecord($erd);
			}
		}

		return $sid;
	}

	//
	function fetch_assoc(){

		$sid = array();

		$sid["syncStop"]		= $this->syncStop;
		$sid["syncNoMultiple"]  = $this->syncNoMultiple;
		$sid["hasEnvelope"]	    = $this->hasEnvelope;
		$sid["hasLoops"]		= $this->hasLoops;
		$sid["hasOutPoint"]	    = $this->hasOutPoint;
		$sid["hasInPoint"]	    = $this->hasInPoint;
		if($sid["hasInPoint"]) $sid["inPoint"]		    = $this->inPoint;
		if($sid["hasOutPoint"]) $sid["outPoint"]		= $this->outPoint;
		if($sid["hasLoops"]) $sid["loopCount"]	    = $this->loopCount;

		if($sid["hasEnvelope"]){
			$sid["envPoints"]	    = $this->envPoints;

			$sid["envelopeRecords"] = array();
			foreach($this->envelopeRecords as $er){
				$sid["envelopeRecords"][] = $er->fetch_assoc();
			}
		}

		return $sid;
	}

	//
	function fetch_xml($includeHeader = true, $tabLevel = 0, $nodeName = "soundInfo"){

		$contentFlag = 0;
		$xmlStr = $includeHeader ? XML_HEADER : "";
		$pfx = get_xml_indent_row(++$tabLevel);

		$xmlStr .= $pfx ."<" .$nodeName ." ";

		if($this->syncStop){
			$xmlStr .= "syncStop=\"1\" ";
			$contentFlag++;
		}
		if($this->syncNoMultiple){
			$xmlStr .= "syncNoMultiple=\"1\" ";
			$contentFlag++;
		}
		if($this->hasLoops){
			$xmlStr .= "loopCount=\"";
			$xmlStr .= $this->loopCount ."\" ";
			$contentFlag++;
		}
		if($this->hasInPoint){
			$xmlStr .= "inPoint=\"";
			$xmlStr .= $this->inPoint ."\" ";
			$contentFlag++;
		}
		if($this->hasOutPoint){
			$xmlStr .= "outPoint=\"";
			$xmlStr .= $this->outPoint ."\" ";
			$contentFlag++;
		}
		if($this->hasEnvelope){
			$xmlStr .= "envPoints=\"";
			$xmlStr .= $this->envPoints ."\" ";
			$contentFlag++;
		}

		$xmlStr .= ">";

		$pfx = get_xml_indent_row(++$tabLevel);

		if($this->hasEnvelope){
			foreach($this->envelopeRecords as $er){
				$xmlStr .= $er->fetch_xml(false, $pfx);
			}
		}

		$pfx = get_xml_indent_row(--$tabLevel);

		$xmlStr .= $pfx ."</" .$nodeName .">";
		if(!$contentFlag) return ""; // empty soundInfo
 		else return $xmlStr;
	}

	//
	function update_buf(){ return $this->set_soundInfo_data(); }
}


/**
 * SOUNDENVELOPE
 */
class MKSoundEnvelope {

	var $buf = "";

	var $pos44;			// UI32 Position of envelope point as a number of 44kHz samples.
						// accordingly if using a sampling rate less than 44kHz.
	var $leftLevel;		// UI16 Volume level for left channel. Minimum is 0, maximum is 32768.
	var $rightLevel;	// UI16 Volume level for right channel. Minimum is 0, maximum is 32768.

	// the constructor
	function MKSoundEnvelope($buf = "", $sed = array()){

		$this->buf = $buf;
		if(strlen($buf) || count($sed)) $this->set_soundEnvelope_data($sed);
	}

	//
	function set_pos44($pos44) { $this->pos44 = $pos44; }
	function get_pos44() { return $this->pos44; }

	//
	function set_multiply($multiply) { $this->multiply = $multiply; }
	function get_multiply() { return $this->multiply; }

	//
	function set_leftLevel($leftLevel) { $this->leftLevel = $leftLevel; }
	function get_leftLevel() { return $this->leftLevel; }

	//
	function set_rightLevel($rightLevel) { $this->rightLevel = $rightLevel; }
	function get_rightLevel() { return $this->rightLevel; }

	//
	function set_soundEnvelope_data($sed = array()){

		if(count($sed)) $this->get_soundEnvelope_data($sed);
		else $sed = $this->fetch_assoc();

		$this->tagData = write_soundEnvelope_data($sed);
		return $this->tagData;
	}

	//
	function get_soundEnvelope_data($sed = array()){

		if(!count($sed)) $sed = read_soundEnvelope_data($this->tagData);

		$this->pos44	  = $sed["pos44"];
		$this->leftLevel  = $sed["leftLevel"];
		$this->rightLevel = $sed["rightLevel"];

		return $sed;
	}

	//
	function fetch_assoc(){

		$sed = array();
		$sed["pos44"]	   = $this->pos44;
		$sed["leftLevel"]  = $this->leftLevel;
		$sed["rightLevel"] = $this->rightLevel;

		return $sed;
	}

	//
	function fetch_xml($includeHeader = true, $tabLevel = 0, $nodeName = "soundEnvelope"){

		$xmlStr = $includeHeader ? XML_HEADER : "";
		$pfx = get_xml_indent_row(++$tabLevel);

		$xmlStr .= $pfx ."<" .$nodeName ." ";

		$xmlStr .= "pos44=\"";
		$xmlStr .= $this->pos44 ."\" ";
		$xmlStr .= "lefLevel=\"";
		$xmlStr .= $this->leftLevel ."\" ";
		$xmlStr .= "rightLevel=\"";
		$xmlStr .= $this->rightLevel ."\" ";

		$xmlStr .= "/>";

		return $xmlStr;
	}

	//
	function update_buf(){
		return $this->set_soundEnvelope_data();
	}
}

/**
 * SoundStreamHead AND SoundStreamHead2
 */
class MKSoundStreamHead extends MKTagHeader {

	// pb prefix: playback
	// ss prefix streamSound

	var $tagData = "";

	var $pbRate = RATE_22;		// UB[2] Playback sampling rate
								// 0 = 5.5 kHz
								// 1 = 11 kHz
								// 2 = 22 kHz
								// 3 = 44 kHz
	var $pbSize = SOUND_16BIT;	// UB[1] Playback sample size.
								// Always 1 (16 bit).
	var $pbType = SOUND_STEREO;	// UB[1] Number of playback channels;
								// mono or stereo.
								// 0 = sndMono
								// 1 = sndStereo
	var $ssCompression = SOUND_MP3;	// UB[4] Format of streaming sound data.
								// 1 = ADPCM
								// SWF 4+ only:
								// 2 = MP3
	var $ssRate = RATE_5K5;	// UB[2] The sampling rate of the streaming
								// sound data
								// 0 = 5.5 kHz
								// 1 = 11 kHz
								// 2 = 22 kHz
								// 3 = 44 kHz
	var $ssSize = SOUND_16BIT;	// UB[1] The sample size of the streaming
								// sound data.
								// Always 1 (16 bit).
	var $ssType = SOUND_MONO;	// UB[1] Number of channels in the
								// streaming sound data
								// 0 = sndMono
								// 1 = sndStereo
	var $ssSampleCount = 0;		// UI16 Average number of samples in each
								// SoundStreamBlock. Not affected
								// by mono/stereo setting; for stereo
								// sounds this is the number of
								// sample pairs.
	var $latencySeek = 0;		// If StreamSoundCompression = 2
								// SI16
								// Otherwise absent
								// See MP3 Sound Data. The value
								// here should match the
								// SeekSamples field in the first
								// SoundStreamBlock for this stream.

	function MKSoundStreamHead($tagData = "", $tagID = SOUND_SH){

		$this->tagData = $tagData;
		$this->tagID = $tagID;

		if(strlen($tagData)){
			parent::MKTagHeader(substr($tagData, 0, 10));
			$this->get_soundStreamHead_data();
		}
	}


	//
	function set_pbRate($pbRate) { $this->pbRate = $pbRate; }
	function get_pbRate() { return $this->pbRate; }

	//
	function set_pbSize($pbSize) { $this->pbSize = $pbSize; }
	function get_pbSize() { return $this->pbSize; }

	//
	function set_pbType($pbType) { $this->pbType = $pbType; }
	function get_pbType() { return $this->pbType; }

	//
	function set_ssCompression($ssCompression) { $this->ssCompression = $ssCompression; }
	function get_ssCompression() { return $this->ssCompression; }

	//
	function set_ssRate($ssRate) { $this->ssRate = $ssRate; }
	function get_ssRate() { return $this->ssRate; }

	//
	function set_ssSize($ssSize) { $this->ssSize = $ssSize; }
	function get_ssSize() { return $this->ssSize; }

	//
	function set_ssType($ssType) { $this->ssType = $ssType; }
	function get_ssType() { return $this->ssType; }

	//
	function set_ssSampleCount($ssSampleCount) { $this->ssSampleCount = $ssSampleCount; }
	function get_ssSampleCount() { return $this->ssSampleCount; }

	//
	function set_latencySeek($latencySeek) { $this->latencySeek = $latencySeek; }
	function get_latencySeek() { return $this->latencySeek; }

	//
	function set_soundStreamHead_data($shd = array()){

		if(count($shd)){
			$this->get_soundStreamHead_data($shd);
		}
		else {
			$shd = $this->fetch_assoc();
		}

		$this->tagData = write_soundStreamHead_data($shd);
		return $this->tagData;
	}

	//
	function get_soundStreamHead_data($shd = array()){

		if(!count($shd)){
			$shd = read_soundStreamHead_data($this->tagData);
		}

		$this->pbRate		 = $shd["pbRate"];
		$this->pbSize		 = $shd["pbSize"];
		$this->pbType		 = $shd["pbType"];

		$this->ssCompression = $shd["ssCompression"];
		$this->ssRate		 = $shd["ssRate"];
		$this->ssSize		 = $shd["ssSize"];
		$this->ssType		 = $shd["ssType"];
		$this->ssSampleCount = $shd["ssSampleCount"];

		if($this->ssCompression == 2)
			$this->latencySeek	 = $shd["latencySeek"];

		return $shd;
	}

	//
	function fetch_assoc(){

		$shd = array();
		$shd["tagID"]		  = $this->tagID;
		$shd["pbRate"]	 	  = $this->pbRate;
		$shd["pbSize"]  	  = $this->pbSize;
		$shd["pbType"]   	  = $this->pbType;
		$shd["ssCompression"] = $this->ssCompression;
		$shd["ssRate"]		  = $this->ssRate;
		$shd["ssSize"]		  = $this->ssSize;
		$shd["ssType"]		  = $this->ssType;
		$shd["ssSampleCount"] = $this->ssSampleCount;
		$shd["latencySeek"]   = $this->latencySeek;

		return $shd;
	}

	//
	function fetch_xml($includeHeader = true, $tabLevel = 0, $nodeName = "streamHead"){

		if($nodeName = "streamHead"){
			if($this->tagID == SOUND_SH2) $nodeName = "streamHead2";
		}

		$xmlStr = $includeHeader ? XML_HEADER : "";
		$pfx = get_xml_indent_row(++$tabLevel);

		$xmlStr .= $pfx ."<" .$nodeName ." ";
		$xmlStr .= "tagSize=\"";
		$xmlStr .= strlen($this->tagData) ."\" >";

		$pfx = get_xml_indent_row(++$tabLevel);

		$xmlStr .= $pfx ."<playbackSettings ";
		$xmlStr .= "rate=\"";
		$xmlStr .=  (get_sound_rate($this->pbRate) / 1000) ."KHz\" ";
		$xmlStr .= "size=\"";
		$xmlStr .= get_sampleSize($this->pbSize) ."\" ";
		$xmlStr .= "type=\"";
		$xmlStr .=  get_sound_type($this->pbType) ."\" ";
		$xmlStr .=  "/>";

		$xmlStr .=  $pfx ."<data>";
		$xmlStr .= hex_dump($this->tagData);
		$xmlStr .=  "</data>";


		$xmlStr .= $pfx ."<streamingSettings ";
		$xmlStr .= "compression=\"";
		$xmlStr .= get_sound_compression($this->ssCompression) ."\" ";
		$xmlStr .= "rate=\"";
		$xmlStr .=  (get_sound_rate($this->ssRate) / 1000) ."KHz\" ";
		$xmlStr .= "size=\"";
		$xmlStr .= get_sampleSize($this->pbSize) ."\" ";
		$xmlStr .= "type=\"";
		$xmlStr .=  get_sound_type($this->ssType) ."\" ";
		$xmlStr .= "sampleCount=\"";
		$xmlStr .=  $this->ssSampleCount ."\" ";

		if($this->ssCompression == 2){
			$xmlStr .= "latencySeek=\"";
			$xmlStr .= $this->latencySeek ."\" ";
		}

		$xmlStr .=  "/>";
		$pfx = get_xml_indent_row(--$tabLevel);

		$xmlStr .= $pfx ."</" .$nodeName .">";
		return $xmlStr;
	}

	//
	function update_tagData(){
		return $this->set_soundStreamHead_data();
	}
}


// microsoft wav classes
// useful to load, embed and save sound data

/**
 * wav: riff chunk (header)
 */
class MKRiff {

	var $riffData = "";

	var $chunkID = "RIFF"; // (0x52494646 big-endian form).

	var $chunkSize = 0; 			// 36 + SubChunk2Size, or more precisely:
									// 4 + (8 + SubChunk1Size) + (8 + SubChunk2Size)
        							// This is the size of the rest of the chunk
        							// following this number.  This is the size of the
        							// entire file in bytes minus 8 bytes for the
        							// two fields not included in this count:
									// ChunkID and ChunkSize.
	var $format = "WAVE";			// Format Contains the letters "WAVE"
        							// (0x57415645 big-endian form).

	// the constructor
	function MKRiff($riffData = "", $chunkSize = 0){
		$this->riffData = $riffData;
		$this->chunkSize = $chunkSize;

		if(strlen($riffData)) $this->get_riff_data();
	}

	//
	function set_chunkSize($chunkSize){ $this->chunkSize = $chunkSize; }
	function get_chunkSize(){ return $this->chunkSize; }

	//
	function set_riff_data($rd = array()){

		if(!count($rd)) $rd = $this->fetch_assoc();
		if(!count($rd)) return ""; // no riff data

		$this->riffData = $rd["chunkID"];
		$this->riffData .= swf_dword($rd["chunkSize"]);
		$this->riffData .= $rd["format"];

		return $this->riffData;
	}

	function get_riff_data(){

		$chunkID = substr($this->riffData, 0, 4);

		if($chunkID != $this->chunkID) return; // no valid RIFF

		$format = substr($this->riffData, 8, 4);

		if($format != $this->format) return; // no valid WAVE

		$this->chunkSize = ui32dec(substr($this->riffData, 4, 4));
	}

	function fetch_assoc(){

		$rd = array();

		if(!$this->chunkSize) return $rd;

		$rd["chunkID"] = $this->chunkID;
		$rd["chunkSize"] = $this->chunkSize;
		$rd["format"] = $this->format;

		return $rd;
	}

	function fetch_xml(){
		/* TODO */

	}

}

/**
 * wav: fmt chunk
 */
class MKFmt {

	var $fmtData = "";

	var $chunkID = "fmt "; // Contains the letters "fmt "
        												// (0x666d7420 big-endian form).

	var $chunkSize = 0;			// 16 for PCM.  This is the size of the
        						// rest of the Subchunk which follows this number.
	var $audioFormat = 1;		// PCM = 1 (i.e. Linear quantization)
        						// Values other than 1 indicate some
        						// form of compression.
	var $numChannels = 1;		// Mono = 1, Stereo = 2, etc.
	var $sampleRate = 44100;	// 8000, 44100, etc.
	var $byteRate;				// SampleRate * NumChannels * BitsPerSample/8
	var $blockAlign;			// == NumChannels * BitsPerSample/8
        						// The number of bytes for one sample including
        						// all channels. I wonder what happens when
        						// this number isn't an integer?
	var $bitsPerSample = 16;	// 8 bits = 8, 16 bits = 16, etc.
	//private var extraParamSize:int;		// if PCM, then doesn't exist
	//private var extraParams:*;			// space for extra parameters


	// the constructor
	function MKFmt($fmtData = ""){
		$this->fmtData = $fmtData;

		if(strlen($fmtData)) $this->get_fmt_data();
	}

	//
	function set_chunkSize($chunkSize){ $this->chunkSize = $chunkSize; }
	function get_chunkSize(){ return $this->chunkSize; }

	//
	function set_audioFormat($audioFormat){ $this->audioFormat = $audioFormat; }
	function get_audioFormat(){ return $this->audioFormat; }

	//
	function set_numChannels($numChannels){ $this->numChannels = $numChannels; }
	function get_numChannels(){ return $this->numChannels; }

	//
	function set_sampleRate($sampleRate){ $this->sampleRate = $sampleRate; }
	function get_sampleRate(){ return $this->sampleRate; }

	//
	function set_byteRate($byteRate){ $this->byteRate = $byteRate; }
	function get_byteRate(){ return $this->byteRate; }

	//
	function set_blockAlign($blockAlign){ $this->blockAlign = $blockAlign; }
	function get_blockAlign(){ return $this->blockAlign; }

	//
	function set_bitsPerSample($bitsPerSample){ $this->bitsPerSample = $bitsPerSample; }
	function get_bitsPerSample(){ return $this->bitsPerSample; }

	//
	function set_fmt_data($fd = array()){

		/* TODO */
		if(!count($fd)) $fd = $this->fetch_assoc();
		if(!count($fd)) return ""; // no fmt valid data

		return $this->fmtData;
	}

	function get_fmt_data(){

		$ofst = 0;
		$chunkID = substr($this->fmtData, $ofst, 4);

		if($chunkID != $this->chunkID) return; // not valid fmt

		$ofst += 4;
		$this->chunkSize = ui32dec(substr($this->fmtData, $ofst, 4));
		$ofst += 4;
		$this->audioFormat = ui16dec(substr($this->fmtData, $ofst, 2));
		$ofst += 2;
		$this->numChannels = ui16dec(substr($this->fmtData, $ofst, 2));
		$ofst += 2;
		$this->sampleRate = ui32dec(substr($this->fmtData, $ofst, 4));
		$ofst += 4;
		$this->byteRate = ui32dec(substr($this->fmtData, $ofst, 4));
		$ofst += 4;
		$this->blockAlign = ui16dec(substr($this->fmtData, $ofst, 2));
		$ofst += 2;
		$this->bitsPerSample = ui16dec(substr($this->fmtData, $ofst, 2));
	}

	function fetch_assoc(){
		$fd = array();

		if(!$this->chunkSize) return $fd; // no fmt valid data
		$this->update_rate_align();

		$fd["chunkID"] = $this->chunkID;
		$fd["chunkSize"] = $this->chunkSize;
		$fd["audioFormat"] = $this->audioFormat;
		$fd["numChannels"] = $this->numChannels;
		$fd["sampleRate"] = $this->sampleRate;
		$fd["byteRate"] = $this->byteRate;
		$fd["blockAlign"] = $this->blockAlign;
		$fd["bitsPerSample"] = $this->bitsPerSample;

		return $fd;
	}

	function fetch_xml(){
		/* TODO */
	}

	//
	function update_rate_align(){
		$this->byteRate = $this->sampleRate * $this->numChannels * $this->bitsPerSample / 8;
		$this->blockAlign = $this->numChannels * $this->bitsPerSample / 8;
	}
}

/**
 * wav: data chunk
 */
class MKSoundData {

	var $dataChunk = "";

	var $chunkID = "data";		// Subchunk2ID Contains the letters "data"
        						// (0x64617461 big-endian form).
	var $chunkSize = 0;			// Subchunk2Size == NumSamples * NumChannels * BitsPerSample/8
        						// This is the number of bytes in the data.
        						// You can also think of this as the size
        						// of the read of the subchunk following this
        						// number.
	var $samples = "";			//The actual sound data

	var $numChannels = 1;
	var $bitsPerSample = 16;


	// the constructor
	function MKSoundData($dataChunk = "", $samples = "", $numChannels = 1, $bitsPerSample = 16){

		$this->dataChunk = $dataChunk;
		$this->samples = $samples;
		$this->numChannels = $numChannels;
		$this->bitsPerSample = $bitsPerSample;

		if(strle($this->dataChunk)) $this->get_sound_data();
	}

	//
	function set_chunkSize($chunkSize){ $this->chunkSize = $chunkSize; }
	function get_chunkSize(){ return $this->chunkSize; }

	//
	function set_sound_data($sdd = array()){
		/* TODO */
	}

	function get_sound_data(){
		/* TODO */
	}

	function fetch_assoc(){
		/* TODO */
		$sdd = array();

		if(!$this->chunkSize) return $sdd;
		return $sdd;
	}

	function fetch_xml(){
		/* TODO */
	}
}


/**
 * wav: container and reader/writer features
 */
class MKWav {

	var $wavData = "";
	var $wavFileName = "";
	var $wavDir = "";

	var $riff;
	var $fmt;
	var $sd;

	// the constructor
	function MKWav($wavData = "", $wavFileName = "", $wavDir = SERVER_MKSND){

		$this->wavData = $wavData;
		$this->wavFileName = $wavFileName;
		$this->wavDir = $wavDir;

		if(strlen($wavData)){
			$this->get_wav_data();
		}
	}

	//
	function set_riff($riff){ $this->riff = $riff; }
	function get_riff(){ return $this->riff; }

	//
	function set_fmt($fmt){ $this->fmt = $fmt; }
	function get_fmt(){ return $this->fmt; }

	//
	function set_soundData($sd){ $this->sd = $sd; }
	function get_sd(){ return $this->sd; }

	//
	function set_wav_data($wd = array()){

		if(!count($wd)) $wd = $this->fetch_assoc();
		if(!count($wd)) return ""; // no wav data

		$this->wavData = $wd["riffData"];
		$this->wavData .= $wd["fmtData"];
		$this->wavData .= $wd["sd"];

		return $this->wavData;
	}

	function get_wav_data(){

		$ofst = 0;
		$riffData = substr($this->wavData, $ofst, RIFF_LENGTH);
		$ofst += RIFF_LENGTH;
		$fmtData = substr($this->wavData, $ofst, FMT_LENGTH);
		$ofst += FMT_LENGTH;
		$dataChunk = substr($this->wavData, $ofst);

		$this->riff = new MKRiff($riffData);
		$this->fmt = new MKfmt($fmtData);
		$this->sd = new MKSoundData($dataChunk);
	}

	//
	function load_from_file($wavFileName, $wavDir = SERVER_MKSND){

		$wavPath = $wavDir .$wavFileName;

		if(!file_exists($wavPath)) return;

		$this->wavFileName = $wavFileName;
		$this->wavDir = $wavDir;

		$fin = fopen($wavPath, "rb");
		$this->wavData = fread($fin, 0, filesize($wavPath));
		fclose($fin);

		$this->get_wav_data();
	}

	//
	function save_to_file($wavFileName, $wavDir = SERVER_MKSND){

		if(!strlen($this->wavData)) $this->set_wav_data();
		if(!strlen($this->wavData)) return; //nothing to save!

		$wavPath = $wavDir .$wavFileName;

		if(!file_exists($wavPath)) return;

		$this->wavFileName = $wavFileName;
		$this->wavDir = $wavDir;

		$this->wavData = $this->set_wav_data();
	}

	//
	function fetch_assoc(){

		$wd = array();

		if(!$this->riff->chunkSize) return $wd;
		if(!$this->fmt->chunkSize) return $wd;
		if(!$this->sd->chunkSize) return $wd;

		$wd["riffData"] = $this->riff->get_riff_data();
		$wd["fmtData"] = $this->fmt->get_fmt_data();
		$wd["sd"] = $this->sd->get_sound_data();

		return $wd;
	}

	function fetch_xml(){
		/* TODO */
	}
}


?>
